# -*- coding: utf-8 -*-
"""
秘塔AI搜索API客户端

功能说明:
- 提供秘塔AI搜索API的封装调用
- 支持网页搜索、内容读取和智能问答功能
- 使用Bearer Token认证方式
- 统一的错误处理和响应格式化

作者: MetasoCreator Team
创建时间: 2025-08-19
"""

import requests
import json
import logging
from typing import Dict, List, Optional, Any
from flask import current_app
from config.config import Config


class MetasoAPIClient:
    """
    秘塔AI搜索API客户端类
    
    提供对秘塔AI搜索服务的完整封装，包括:
    - 网页搜索功能
    - 网页内容读取
    - 智能问答功能
    """
    
    def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None):
        """
        初始化秘塔API客户端
        
        Args:
            api_key: API密钥，如果不提供则从配置中获取
            base_url: API基础URL，如果不提供则使用默认值
        """
        self.api_key = api_key or current_app.config.get('METASO_API_KEY')
        self.base_url = base_url or current_app.config.get('METASO_API_BASE_URL', 'https://metaso.cn/api/v1')
        self.timeout = current_app.config.get('METASO_TIMEOUT', 30)
        self.session = requests.Session()
        
        # 设置默认请求头
        self.session.headers.update({
            'Authorization': f'Bearer {self.api_key}',
            'Content-Type': 'application/json',
            'User-Agent': 'MetasoCreator/1.0'
        })
        
        # 初始化日志记录器
        self.logger = logging.getLogger(__name__)
        
        # 记录初始化信息
        print(f"[秘塔客户端] 初始化完成 - base_url: {self.base_url}, timeout: {self.timeout}s")
        if self.api_key:
            print(f"[秘塔客户端] API密钥已配置，长度: {len(self.api_key)}")
        else:
            print(f"[秘塔客户端] API密钥未配置，请检查环境变量 METASO_API_KEY")
        
    def _process_scope_parameter(self, scope: Optional[str], valid_scopes: List[str]) -> Optional[str]:
        """
        处理scope参数，支持多个值
        
        Args:
            scope: 原始scope参数，可以是单个值、逗号分隔的字符串或None
            valid_scopes: 有效的scope值列表
            
        Returns:
            str: 处理后的scope参数，多个值用逗号分隔
            
        Raises:
            MetasoAPIError: 当scope值无效时抛出
        """
        if not scope:
            return scope
            
        # 如果是字符串，按逗号分割
        if isinstance(scope, str):
            scope_list = [s.strip() for s in scope.split(',') if s.strip()]
        else:
            # 如果是列表或其他可迭代对象
            try:
                scope_list = [str(s).strip() for s in scope if str(s).strip()]
            except (TypeError, AttributeError):
                scope_list = [str(scope).strip()]
        
        # 验证每个scope值
        invalid_scopes = [s for s in scope_list if s not in valid_scopes]
        if invalid_scopes:
            raise MetasoAPIError(f"无效的搜索范围: {', '.join(invalid_scopes)}，有效值为: {', '.join(valid_scopes)}")
        
        # 去重并保持顺序
        unique_scopes = []
        for s in scope_list:
            if s not in unique_scopes:
                unique_scopes.append(s)
        
        # 返回逗号分隔的字符串
        return ','.join(unique_scopes) if unique_scopes else None
        
    def search(self, query: str, scope: Optional[str] = "webpage", 
               include_summary: Optional[bool] = False, page: Optional[int] = None, 
               size: Optional[int] = None, **kwargs) -> Dict[str, Any]:
        """
        执行搜索查询
        
        Args:
            query: 搜索查询关键词（必填）
            scope: 搜索范围，可选值: 'webpage'(网页), 'document'(文档), 'scholar'(学术), 
                  'podcast'(播客), 'image'(图片), 'video'(视频)，默认为'webpage'
                  支持多个值：可以传入逗号分隔的字符串（如"webpage,scholar"）或数组
            include_summary: 为true时会以网页的摘要信息匹配更多来源，默认为false
            page: 页码，默认为1，当传入page参数时，同时需要传入size参数，最多10条
            size: 本次取多少条结果，最大值200，size参数和page参数不可同时出现
            **kwargs: 其他搜索参数
            
        Returns:
            Dict: 搜索结果数据
            
        Raises:
            MetasoAPIError: API调用失败时抛出
        """
        # 参数验证
        if not query or not query.strip():
            raise MetasoAPIError("搜索查询关键词不能为空")
            
        # 处理scope参数，支持多个值
        valid_scopes = ['webpage', 'document', 'scholar', 'podcast', 'image', 'video']
        processed_scope = self._process_scope_parameter(scope, valid_scopes)
            
        # 验证page和size参数的互斥性
        if page is not None and size is not None:
            raise MetasoAPIError("page参数和size参数不可同时使用")
            
        # 验证size参数范围
        if size is not None and (size < 1 or size > 200):
            raise MetasoAPIError("size参数必须在1-200之间")
            
        endpoint = f"{self.base_url}/search"
        
        # 构建请求参数
        payload = {
            'q': query.strip()
        }
        
        # 添加可选参数
        if processed_scope:
            payload['scope'] = processed_scope
        if include_summary is not None:
            payload['includeSummary'] = include_summary
        if page is not None:
            payload['page'] = page
        if size is not None:
            payload['size'] = size
            
        # 添加其他参数
        payload.update(kwargs)
        
        try:
            print(f"[秘塔客户端] 发起搜索请求 - 查询: '{query}', 端点: {endpoint}")
            print(f"[秘塔客户端] 请求参数: {json.dumps(payload, ensure_ascii=False)}")
            print(f"[秘塔客户端] 请求头: Authorization=Bearer {self.api_key[:10]}...")
            
            response = self.session.post(endpoint, json=payload, timeout=self.timeout)
            
            print(f"[秘塔客户端] 收到响应 - 状态码: {response.status_code}, 响应时间: {response.elapsed.total_seconds():.2f}s")
            
            # 检查响应状态
            if response.status_code == 200:
                result = response.json()
                print(f"[秘塔客户端] 搜索成功 - 响应数据键: {list(result.keys()) if isinstance(result, dict) else 'N/A'}")
                
                if isinstance(result, dict):
                    webpages_count = len(result.get('webpages', []))
                    total_count = result.get('total', 0)
                    print(f"[秘塔客户端] 搜索结果详情 - webpages: {webpages_count}, total: {total_count}")
                    
                    # 记录前几个结果的标题（用于调试）
                    webpages = result.get('webpages', [])
                    for i, webpage in enumerate(webpages[:3]):
                        title = webpage.get('title', '')[:50]
                        url = webpage.get('link', '')
                        print(f"[秘塔客户端] 结果{i+1}: {title}... - {url}")
                
                return result
            else:
                try:
                    error_response = response.json()
                    print(f"[秘塔客户端] 搜索请求失败 - 状态码: {response.status_code}, 错误响应: {json.dumps(error_response, ensure_ascii=False)}")
                except:
                    print(f"[秘塔客户端] 搜索请求失败 - 状态码: {response.status_code}, 响应文本: {response.text[:200]}")
                
                error_msg = f"搜索请求失败: HTTP {response.status_code}"
                raise MetasoAPIError(error_msg, response.status_code)
                
        except requests.exceptions.RequestException as e:
            error_msg = f"网络请求异常: {str(e)}"
            print(f"[秘塔客户端] {error_msg}")
            raise MetasoAPIError(error_msg)
            
    def get_content(self, url: str) -> Dict[str, Any]:
        """
        获取网页内容
        
        Args:
            url: 要读取的网页URL（必填）
            
        Returns:
            Dict: 网页内容数据，以markdown格式返回
            
        Raises:
            MetasoAPIError: API调用失败时抛出
        """
        # 参数验证
        if not url or not url.strip():
            raise MetasoAPIError("网页URL不能为空")
            
        # 简单的URL格式验证
        if not (url.startswith('http://') or url.startswith('https://')):
            raise MetasoAPIError("URL必须以http://或https://开头")
            
        endpoint = f"{self.base_url}/reader"
        
        # 使用GET方法，参数通过查询字符串传递
        params = {'url': url.strip()}
        
        try:
            print(f"[秘塔客户端] 获取网页内容 - URL: {url}, 端点: {endpoint}")
            print(f"[秘塔客户端] 请求参数: {params}")
            
            response = self.session.get(endpoint, params=params, timeout=30)
            
            print(f"[秘塔客户端] 收到响应 - 状态码: {response.status_code}, 响应时间: {response.elapsed.total_seconds():.2f}s")
            
            if response.status_code == 200:
                result = response.json()
                print(f"[秘塔客户端] 网页内容获取成功 - 响应数据键: {list(result.keys()) if isinstance(result, dict) else 'N/A'}")
                
                if isinstance(result, dict):
                    # 秘塔API返回的内容字段名是'markdown'
                    content_length = len(result.get('markdown', '') or result.get('content', ''))
                    title = result.get('title', '')[:50]
                    print(f"[秘塔客户端] 内容详情 - 标题: '{title}...', 内容长度: {content_length}字符")
                
                return result
            else:
                try:
                    error_response = response.json()
                    print(f"[秘塔客户端] 内容获取失败 - 状态码: {response.status_code}, 错误响应: {json.dumps(error_response, ensure_ascii=False)}")
                except:
                    print(f"[秘塔客户端] 内容获取失败 - 状态码: {response.status_code}, 响应文本: {response.text[:200]}")
                
                error_msg = f"内容获取失败: HTTP {response.status_code}"
                raise MetasoAPIError(error_msg, response.status_code)
                
        except requests.exceptions.RequestException as e:
            error_msg = f"网络请求异常: {str(e)}"
            print(f"[秘塔客户端] {error_msg}")
            raise MetasoAPIError(error_msg)
            
    def ask_question(self, messages: List[Dict[str, str]], scope: Optional[str] = None, 
                    model: Optional[str] = None) -> Dict[str, Any]:
        """
        智能问答功能
        
        Args:
            messages: 对话消息列表（必填），格式为[{"role": "user", "content": "问题内容"}]
            scope: 搜索范围，可选值: 'webpage'(网页), 'document'(文档), 'scholar'(学术), 
                  'podcast'(播客), 'image'(图片), 'video'(视频)，默认为'webpage'
            model: 使用模型，可选值: 'fast_thinking'(快速思考模型，响应速度快，推理能力一般), 
                  'da_vinci'(达芬奇模型，响应速度一般，推理能力强)，默认为'fast_thinking'
            
        Returns:
            Dict: 问答结果
            
        Raises:
            MetasoAPIError: API调用失败时抛出
        """
        # 参数验证
        if not messages or not isinstance(messages, list):
            raise MetasoAPIError("messages参数必须是非空列表")
            
        # 验证messages格式
        for i, message in enumerate(messages):
            if not isinstance(message, dict):
                raise MetasoAPIError(f"messages[{i}]必须是字典类型")
            if 'role' not in message or 'content' not in message:
                raise MetasoAPIError(f"messages[{i}]必须包含'role'和'content'字段")
            if message['role'] not in ['user', 'assistant', 'system']:
                raise MetasoAPIError(f"messages[{i}].role必须是'user'、'assistant'或'system'")
                
        # 验证scope参数
        if scope:
            valid_scopes = ['webpage', 'document', 'scholar', 'podcast', 'image', 'video']
            if scope not in valid_scopes:
                raise MetasoAPIError(f"无效的搜索范围: {scope}，有效值为: {', '.join(valid_scopes)}")
                
        # 验证model参数
        if model:
            valid_models = ['fast_thinking', 'da_vinci']
            if model not in valid_models:
                raise MetasoAPIError(f"无效的模型: {model}，有效值为: {', '.join(valid_models)}")
                
        endpoint = f"{self.base_url}/chat/completions"
        
        payload = {'messages': messages}
        
        # 添加可选参数
        if scope:
            payload['scope'] = scope
        if model:
            payload['model'] = model
            
        try:
            # 获取最后一条用户消息用于日志
            last_user_message = ""
            for message in reversed(messages):
                if message.get('role') == 'user':
                    last_user_message = message.get('content', '')[:100]  # 截取前100字符
                    break
                    
            print(f"[秘塔客户端] 智能问答请求 - 问题: '{last_user_message}...', 端点: {endpoint}")
            print(f"[秘塔客户端] 请求参数 - messages数量: {len(messages)}, scope: {scope}, model: {model}")
            print(f"[秘塔客户端] 完整消息列表: {json.dumps(messages, ensure_ascii=False)}")
            
            response = self.session.post(endpoint, json=payload, timeout=30)
            
            print(f"[秘塔客户端] 收到响应 - 状态码: {response.status_code}, 响应时间: {response.elapsed.total_seconds():.2f}s")
            
            if response.status_code == 200:
                result = response.json()
                print(f"[秘塔客户端] 智能问答成功 - 响应数据键: {list(result.keys()) if isinstance(result, dict) else 'N/A'}")
                
                if isinstance(result, dict):
                    choices = result.get('choices', [])
                    if choices and len(choices) > 0:
                        answer_content = choices[0].get('message', {}).get('content', '')[:100]
                        print(f"[秘塔客户端] 回答内容预览: '{answer_content}...'")
                    
                    usage = result.get('usage', {})
                    if usage:
                        print(f"[秘塔客户端] Token使用情况: {usage}")
                
                return result
            else:
                try:
                    error_response = response.json()
                    print(f"[秘塔客户端] 问答请求失败 - 状态码: {response.status_code}, 错误响应: {json.dumps(error_response, ensure_ascii=False)}")
                except:
                    print(f"[秘塔客户端] 问答请求失败 - 状态码: {response.status_code}, 响应文本: {response.text[:200]}")
                
                error_msg = f"问答请求失败: HTTP {response.status_code}"
                raise MetasoAPIError(error_msg, response.status_code)
                
        except requests.exceptions.RequestException as e:
            error_msg = f"网络请求异常: {str(e)}"
            print(f"[秘塔客户端] {error_msg}")
            raise MetasoAPIError(error_msg)
            
    def test_connection(self) -> bool:
        """
        测试API连接
        
        Returns:
            bool: 连接是否成功
        """
        try:
            # 使用简单的搜索请求测试连接
            result = self.search("test", size=1)
            return True
        except Exception as e:
            print(f"API连接测试失败: {str(e)}")
            return False
            
    def close(self):
        """
        关闭会话
        """
        if self.session:
            self.session.close()
            
    def __enter__(self):
        """上下文管理器入口"""
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器出口"""
        self.close()


class MetasoAPIError(Exception):
    """
    秘塔API异常类
    """
    
    def __init__(self, message: str, status_code: Optional[int] = None):
        self.message = message
        self.status_code = status_code
        super().__init__(self.message)
        
    def __str__(self):
        if self.status_code:
            return f"MetasoAPIError({self.status_code}): {self.message}"
        return f"MetasoAPIError: {self.message}"


# 全局实例变量
metaso_client = None

def get_metaso_client():
    """
    获取MetasoAPIClient实例
    
    Returns:
        MetasoAPIClient: API客户端实例
    """
    global metaso_client
    if metaso_client is None:
        metaso_client = MetasoAPIClient()
    return metaso_client


# 便捷函数
def search_web(query: str, scope: Optional[str] = "webpage", 
               include_summary: Optional[bool] = False, **kwargs) -> Dict[str, Any]:
    """
    便捷的网页搜索函数
    
    Args:
        query: 搜索关键词
        scope: 搜索范围，默认为'webpage'
        include_summary: 是否包含摘要，默认为False
        **kwargs: 其他搜索参数
        
    Returns:
        Dict: 搜索结果
    """
    client = get_metaso_client()
    return client.search(query, scope=scope, include_summary=include_summary, **kwargs)


def get_web_content(url: str) -> Dict[str, Any]:
    """
    便捷的网页内容获取函数
    
    Args:
        url: 网页URL
        
    Returns:
        Dict: 网页内容
    """
    client = get_metaso_client()
    return client.get_content(url)


def ask_ai(question: str, scope: Optional[str] = None, 
           model: Optional[str] = None, context_messages: Optional[List[Dict[str, str]]] = None) -> Dict[str, Any]:
    """
    便捷的AI问答函数
    
    Args:
        question: 用户问题
        scope: 搜索范围，可选
        model: 使用的模型，可选
        context_messages: 上下文消息列表，可选
        
    Returns:
        Dict: 问答结果
    """
    # 构建messages格式
    messages = []
    
    # 添加上下文消息
    if context_messages:
        messages.extend(context_messages)
        
    # 添加当前问题
    messages.append({"role": "user", "content": question})
    
    client = get_metaso_client()
    return client.ask_question(messages, scope=scope, model=model)